﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Lexer;

namespace CSharpLexer {
	public class CSharpLexer {

		private static ICollection<string> keywords =
				// C# spec §2.4.3
			(@"	abstract as		base			bool			break
				byte			case			catch			char			checked
				class			const			continue		decimal			default
				delegate		do				double			else			enum
				event			explicit		extern			false			finally
				fixed			float			for				foreach			goto
				if				implicit		in				int				interface
				internal		is				lock			long			namespace
				new				null			object			operator		out
				override		params			private			protected		public
				readonly		ref				return			sbyte			sealed
				short			sizeof			stackalloc		static			string
				struct			switch			this			throw			true
				try				typeof			uint			ulong			unchecked
				unsafe			ushort			using			virtual			void
				volatile		while"
				+ // Boolean literals, C# spec §2.4.4.1
				"true			false"
				+ // null literal, C# spec §2.4.4.6
				"null"
			).Split('\t', '\n', ' ', '\r').Where(s => s != null && s.Length > 0).ToDictionary((s) => s).Keys;

		private static ICollection<string> operators =
			// C# spec §2.4.5
			(@"	{		}		[		]		(		)		.		,		:		;
				+		-		*		/		%		&		|		^		!		~
				=		<		>		?		??		::		++		--		&&		||
				->		==		!=		<=		>=		+=		-=		*=		/=		%=
				&=		|=		^=		<<		<<=	=>	"
				+ // Right-shift
				">>"
				+ // Right-shift-assignment
				">>="
			).Split('\t', '\n', ' ', '\r').Where(s => s != null && s.Length > 0).ToDictionary((s) => s).Keys;

		private class Chars {
			public static readonly Regex EOL = // C# spec §2.3.1
				new Regex("[\u000A\u0085\u2028\u2029\u000D\u000A]");
			public static readonly Regex NOT_EOL =
				new Regex("[^\u000A\u0085\u2028\u2029\u000D\u000A]");
			public static readonly Regex WHITESPACE = // C# spec §2.3.3
				new Regex(@"[\p{Zs}\u0009\u000B\u000C]");
			public static readonly Regex LETTER = // C# spec §2.4.2
				new Regex(@"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}]");
			public static readonly Regex CONNECTER_COMBINER_FORMATTER = // C# spec §2.4.2
				new Regex(@"[\p{Pc}\p{Mn}\p{Mc}\p{Cf}]");
			public static readonly Regex DIGIT = // C# spec §2.4.2
				new Regex(@"\p{Nd}");
			public static readonly Regex HEX_DIGIT = // C# spec 2.4.4.2
				new Regex("[0-9abcdef]", RegexOptions.IgnoreCase);
			public static Regex UNDERSCORE = new Regex("_");
			public static Regex BACKSLASH = new Regex(@"\\");
			public static Regex SLASH = new Regex("/");
			public static Regex STAR = new Regex(@"\*");
			public static Regex NOT_STAR = new Regex("[^*]");
			public static Regex QUOTE_SINGLE = new Regex("'");
			public static Regex QUOTE_DOUBLE = new Regex("\"");
			public static Regex AT = new Regex("@");
			public static Regex ANYCASE_E = new Regex("e", RegexOptions.IgnoreCase);
			public static Regex ANYCASE_L = new Regex("l", RegexOptions.IgnoreCase);
			public static Regex ANYCASE_U = new Regex("u", RegexOptions.IgnoreCase);
			public static Regex REAL_SUFFIX = new Regex("[fdm]", RegexOptions.IgnoreCase);
			public static Regex SIGN = new Regex("[-+]");
			public static Regex SINGLE_CHARACTER = new Regex("[^'\\\u000A\u0085\u2028\u2029\u000D\u000A]");
		}

		private Lexer<Token> lexer;

		private void RegisterPatterns(Token.TokenType tokenType, object[] patterns) {
			TokenCreator<Token> creator = (pos, lexeme) => {
				return new Token(tokenType, lexeme, pos, pos + lexeme.Length);
			};
			foreach (object pattern in patterns) {
				if (! (pattern is object[])) {
					lexer.RegisterPattern(new LexerPattern(pattern), creator);
				} else {
					// Allow nested patterns - by flattening.
					lexer.RegisterPattern(new LexerPattern(flatten((object[])pattern).ToArray()), creator);
				}
			}
		}

		private IEnumerable<object> flatten(object[] objs) {
			foreach (object obj in objs) {
				if (obj is object[]) {
					foreach (object inner in flatten((object[])obj)) {
						yield return inner;
					}
				} else {
					yield return obj;
				}
			}
		}

		private IEnumerable<string> escapeCollection(IEnumerable<string> strings) {
			return strings.Select(s => {
				if (LexerPattern.MetaChars.Contains('\\')) {
					s = s.Replace(@"\", @"\\");
				}
				foreach (char meta in LexerPattern.MetaChars) {
					if (meta == '\\') continue;
					s = s.Replace(meta.ToString(), @"\" + meta.ToString());
				}
				return s;
			});
		}

		private void SetupLexer() {
			lexer = new Lexer<Token>();
			lexer.UnrecognizedCallback = (pos, lexeme) => new Token(Token.TokenType.ERROR, lexeme, pos, pos + lexeme.Length);
			lexer.EofCallback = (pos, lexeme) => new Token(Token.TokenType.EOF, lexeme, pos, pos);

			RegisterPatterns(Token.TokenType.OPERATOR_OR_PUNCTUATOR, escapeCollection(operators).ToArray());
			RegisterPatterns(Token.TokenType.KEYWORD, escapeCollection(keywords).ToArray());
			lexer.RegisterPattern(new LexerPattern(Chars.WHITESPACE, "+"), (pos, lexeme) => null);
			RegisterPatterns(Token.TokenType.COMMENT, new object[][] {
				new object[] { "//", Chars.NOT_EOL, "+", Chars.EOL },
				new object[] { "/*", Chars.NOT_STAR, Chars.STAR, "+/" },
			});
			var digits = new object[] { Chars.DIGIT, "+" };
			var hex_digits = new object[] { "0x", Chars.HEX_DIGIT, "+" };
			RegisterPatterns(Token.TokenType.INTEGRAL_LITERAL, new object[][] { // C# spec §2.4.4.2
				new object[] { digits },
				new object[] { digits, Chars.ANYCASE_U },
				new object[] { digits, Chars.ANYCASE_L },
				new object[] { digits, Chars.ANYCASE_U, Chars.ANYCASE_L },
				new object[] { digits, Chars.ANYCASE_L, Chars.ANYCASE_U },
				new object[] { hex_digits },
				new object[] { hex_digits, Chars.ANYCASE_U },
				new object[] { hex_digits, Chars.ANYCASE_L },
				new object[] { hex_digits, Chars.ANYCASE_U, Chars.ANYCASE_L },
				new object[] { hex_digits, Chars.ANYCASE_L, Chars.ANYCASE_U },
			});
			var unsigned_exponent = new object[] { Chars.ANYCASE_E, digits };
			var signed_exponent = new object[] { Chars.ANYCASE_E, Chars.SIGN, digits };
			var dot_digits = new object[] { ".", digits };
			var digits_dot_digits = new object[] { digits, ".", digits };
			RegisterPatterns(Token.TokenType.REAL_LITERAL, new object[][] { // C# spec §2.4.4.3
				// .123
				new object[] { dot_digits },
				// .123f
				new object[] { dot_digits, Chars.REAL_SUFFIX },
				// .123e456
				new object[] { dot_digits, unsigned_exponent },
				// .123e+456
				new object[] { dot_digits, signed_exponent },
				// .123e456f
				new object[] { dot_digits, unsigned_exponent, Chars.REAL_SUFFIX },
				// .123e+456f
				new object[] { dot_digits, signed_exponent, Chars.REAL_SUFFIX },
				// 123e456
				new object[] { digits, unsigned_exponent },
				// 123e+456
				new object[] { digits, signed_exponent },
				// 123e456f
				new object[] { digits, unsigned_exponent, Chars.REAL_SUFFIX },
				// 123e+456f
				new object[] { digits, signed_exponent, Chars.REAL_SUFFIX },
				// 123f
				new object[] { digits, Chars.REAL_SUFFIX },
				// 123.456
				new object[] { digits_dot_digits },
				// 123.456f
				new object[] { digits_dot_digits, Chars.REAL_SUFFIX },
				// 123.456e789
				new object[] { digits_dot_digits, unsigned_exponent },
				// 123.456e+789
				new object[] { digits_dot_digits, signed_exponent },
				// 123.456e789f
				new object[] { digits_dot_digits, unsigned_exponent, Chars.REAL_SUFFIX },
				// 123.456e+789f
				new object[] { digits_dot_digits, signed_exponent, Chars.REAL_SUFFIX },
			});
			RegisterPatterns(Token.TokenType.CHARACTER_LITERAL, new object[][] { // C# spec §2.4.4.4
				new object[] { "'", Chars.SINGLE_CHARACTER, "'" },
				new object[] { @"'\''" },
				new object[] { "'\\\"'" },
				new object[] { @"'\\'" },
				new object[] { @"'\0'" },
				new object[] { @"'\a'" },
				new object[] { @"'\b'" },
				new object[] { @"'\f'" },
				new object[] { @"'\n'" },
				new object[] { @"'\r'" },
				new object[] { @"'\t'" },
				new object[] { @"'\v'" },
				new object[] { @"'\x'", Chars.HEX_DIGIT },
				new object[] { @"'\x'", Chars.HEX_DIGIT, Chars.HEX_DIGIT },
				new object[] { @"'\x'", Chars.HEX_DIGIT, Chars.HEX_DIGIT, Chars.HEX_DIGIT },
				new object[] { @"'\x'", Chars.HEX_DIGIT, Chars.HEX_DIGIT, Chars.HEX_DIGIT, Chars.HEX_DIGIT },
				// missing Unicode escape sequence!
			});
			RegisterPatterns(Token.TokenType.STRING_LITERAL, new object[][] { // C# spec §2.4.4.5
				new object[] { "\"\"" },
				new object[] { "@\"\"" },
			});
			RegisterPatterns(Token.TokenType.IDENTIFIER, new object[][] { // C# spec §2.4.2
				new object[] { Chars.LETTER
			});
		}

		public CSharpLexer() {
			SetupLexer();
		}

		public IEnumerable<Token> Scan(string input) {
			return lexer.Scan(input);
		}
	}
}
